using TerritoryGame.Domain.Common;
using TerritoryGame.Domain.Entities;
using TerritoryGame.Domain.Events;
using TerritoryGame.Domain.ValueObjects;
using TerritoryGame.Domain.Services;

namespace TerritoryGame.Domain.Entities;

public class Game : AggregateRoot<Guid>
{
    public GameRoom Room { get; private set; }
    public Canvas Canvas { get; private set; }
    public DateTime CreatedAt { get; private set; }
    private readonly IGameStateService _stateService;
    private readonly IAreaCalculationService _areaService;
    
    private Game() { }
    
    public Game(Guid id, GameRoom room, IGameStateService stateService = null, IAreaCalculationService areaService = null) : base(id)
    {
        Room = room ?? throw new ArgumentNullException(nameof(room));
        _stateService = stateService ?? new GameStateService();
        _areaService = areaService ?? new AreaCalculationService();
        Canvas = new Canvas(800, 600, _areaService);
        CreatedAt = DateTime.UtcNow;
    }
    
    public void StartGame()
    {
        if (!_stateService.CanStartGame(this))
            throw new InvalidOperationException("Cannot start game in current state");
            
        _stateService.StartGame(this);
        Room.StartGame();
    }
    
    public int ProcessPaintAction(Guid playerId, Position position, int brushSize)
    {
        if (Room.Status != GameStatus.Playing)
            throw new InvalidOperationException("Game is not in playing state");
            
        var player = Room.Players.FirstOrDefault(p => p.Id == playerId);
        if (player == null)
            throw new InvalidOperationException("Player not found in game");
            
        if (!Canvas.CanPaint(position, brushSize, player.Color))
            throw new InvalidOperationException("Cannot paint at this position");
            
        int areaChange = Canvas.Paint(position, brushSize, player.Color);
        
        // 更新玩家面积
        player.UpdateArea(areaChange);
        
        AddDomainEvent(new AreaCapturedEvent(Id, playerId, position, brushSize, areaChange));
        
        return areaChange;
    }
    
    public void EndGame()
    {
        if (!_stateService.CanEndGame(this))
            throw new InvalidOperationException("Cannot end game in current state");
            
        _stateService.EndGame(this);
        Room.EndGame();
    }
    
    public Dictionary<Guid, GameScore> CalculateFinalScores()
    {
        var scores = new Dictionary<Guid, GameScore>();
        
        foreach (var player in Room.Players)
        {
            var area = Canvas.GetPlayerArea(player.Color);
            scores[player.Id] = new GameScore(area);
        }
        
        return scores;
    }
    
    public TerritoryGame.Domain.Enums.GameState GetCurrentState()
    {
        return _stateService.GetCurrentState(this);
    }
    
    public TimeSpan GetRemainingTime()
    {
        return _stateService.GetRemainingTime(this);
    }
    
    public bool IsGameFinished()
    {
        return _stateService.IsGameFinished(this);
    }
    
    public Player GetWinner()
    {
        return _stateService.GetWinner(this);
    }
    
    public void PauseGame()
    {
        _stateService.PauseGame(this);
    }
    
    public void ResumeGame()
    {
        _stateService.ResumeGame(this);
    }
}

public class Canvas
{
    public int Width { get; private set; }
    public int Height { get; private set; }
    public Dictionary<Position, PlayerColor> Pixels { get; private set; }
    private readonly IAreaCalculationService _areaService;
    private readonly IConflictResolutionService _conflictService;
    
    private Canvas() { }
    
    public Canvas(int width, int height, IAreaCalculationService areaService = null, IConflictResolutionService conflictService = null)
    {
        Width = width;
        Height = height;
        Pixels = new Dictionary<Position, PlayerColor>();
        _areaService = areaService ?? new AreaCalculationService();
        _conflictService = conflictService ?? new ConflictResolutionService();
    }
    
    public bool CanPaint(Position position, int brushSize, PlayerColor color)
    {
        return _conflictService.CanPaintAtPosition(position, brushSize, color, Pixels, Width, Height);
    }
    
    public int Paint(Position position, int brushSize, PlayerColor color)
                {
        if (!_conflictService.IsValidPaintAction(position, brushSize, color, Pixels, Width, Height))
            throw new InvalidOperationException("Invalid paint action");
            
        if (!CanPaint(position, brushSize, color))
            throw new InvalidOperationException("Cannot paint at this position");
            
        int areaChange = _conflictService.CalculateEffectiveAreaChange(position, brushSize, color, Pixels);
        var affectedPixels = GetAffectedPixels(position, brushSize);
        
        foreach (var pixelPos in affectedPixels)
        {
                if (pixelPos.IsWithinBounds(Width, Height))
                {
                    Pixels[pixelPos] = color;
            }
        }
        
        // 更新面积缓存
        _areaService.UpdateAreaCache(color, areaChange);
        
        return areaChange;
    }
    
    public int GetPlayerArea(PlayerColor color)
    {
        return _areaService.CalculatePlayerArea(color, Pixels);
    }
    
    public Dictionary<PlayerColor, int> GetAllAreas()
    {
        return _areaService.CalculateAllAreas(Pixels);
    }
    
    public Dictionary<PlayerColor, int> GetCachedAreas()
    {
        return _areaService.GetCachedAreas();
    }
    
    private static IEnumerable<Position> GetAffectedPixels(Position center, int brushSize)
    {
        var pixels = new List<Position>();
        int radius = brushSize / 2;
        
        for (int x = center.X - radius; x <= center.X + radius; x++)
        {
            for (int y = center.Y - radius; y <= center.Y + radius; y++)
            {
                // 圆形画笔
                if (Math.Pow(x - center.X, 2) + Math.Pow(y - center.Y, 2) <= Math.Pow(radius, 2))
                {
                    pixels.Add(new Position(x, y));
                }
            }
        }
        
        return pixels;
    }
}
